/**
 * \file Datatype.cpp
 *
 * ICC General Code Base
 * Written 2010-2011, by Anthony Berlin and Alexander Carlqvist
 *
 * This file is part of ICC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * \bug Nested function parameters ("int(int()*)" for example) don't work
 *   at the moment, when creating a Datatype instance from a string. We only
 *   enter an infinite loop.
 */

#include "Datatype.h"

#include <iterator>
#include <boost/foreach.hpp>

#define foreach BOOST_FOREACH

///
/// Constructors for 'Datatype'.
///
Datatype::Datatype()
: m_flags(0), m_lexeme(""), m_isFunction(false), m_functionParameters(NULL), m_isPointer(false),
    m_isArray(false), m_pointer(NULL), m_isReference(false)
{
    m_functionParameters = new (GC) list<Datatype*>;
}

Datatype::Datatype(unsigned long int flags, bool isFunc, list<Datatype*>* funcParamDatatypes,
                   bool isPointer, bool isArray, string* lexeme, bool isRef)
: m_flags(flags), m_isFunction(isFunc), m_functionParameters(funcParamDatatypes),
    m_isPointer(isPointer), m_isArray(isArray), m_pointer(NULL), m_isReference(isRef)
{
    m_functionParameters = new (GC) list<Datatype*>;
    m_lexeme = string(*lexeme);
}

Datatype::Datatype(unsigned long int flags, bool isFunc, list<Datatype*>* funcParamDatatypes,
                   bool isPointer, bool isArray, const char* lexeme, bool isRef)
: m_flags(flags), m_isFunction(isFunc), m_functionParameters(funcParamDatatypes),
    m_isPointer(isPointer), m_isArray(isArray), m_pointer(NULL), m_isReference(isRef)
{
    m_functionParameters = new (GC) list<Datatype*>;
    m_lexeme = string(lexeme);
}

Datatype::Datatype(unsigned long int flags, bool isFunc, list<Datatype*>* funcParamDatatypes,
                   bool isPointer, bool isArray, string lexeme, bool isRef)
: m_flags(flags), m_lexeme(lexeme), m_isFunction(isFunc),
    m_functionParameters(funcParamDatatypes), m_isPointer(isPointer), m_isArray(isArray),
    m_pointer(NULL), m_isReference(isRef)
{
    m_functionParameters = new (GC) list<Datatype*>;
}

// Create the datatype from a pointer to a string instead.
Datatype::Datatype(string* from_string, bool isParameter)
: m_flags(0), m_lexeme(""), m_isFunction(false), m_functionParameters(NULL), m_isPointer(false),
    m_isArray(false), m_pointer(NULL), m_isReference(false)
{
    m_functionParameters = new (GC) list<Datatype*>;

    if (*from_string == "")
        return;

    if (*from_string == UNKNOWN_TYPE)
    {
        m_lexeme = UNKNOWN_TYPE;
        return;
    }

    list<string> tokens;
    string token = "";

    // Tokenize the string.
    foreach (char chr, *from_string)
    {
        if (chr != ' ' && chr != '\t')
            token += chr;

        if (token == "void" || token == "int" || token == "short int" || token == "char" ||
            token == "short short int" || token == "long int" || token == "long long int" ||
            token == "bool" || token == "float" || token == "double" || token == "long double" ||
            token == "string" || token == "const" || token == "signed" || token == "unsigned" ||
            token == "&" || token == "*" || token == "(" || token == ")" || token == "[]" ||
            token == ",")
        {
            tokens.push_back(token);
            token = "";
        }
    }

    Datatype* datatype = this;

    // Parse the tokens and setup the datatype.
    for (list<string>::iterator it = tokens.begin(); it != tokens.end(); it++)
    {
        if (*it == "void")
            datatype->m_flags |= DT_VOID;
        else if (*it == "bool")
            datatype->m_flags |= DT_BOOL;
        else if (*it == "int")
            datatype->m_flags |= DT_INT;
        else if (*it == "short int")
            datatype->m_flags |= DT_SHORT_INT;
        else if (*it == "short short int")
            datatype->m_flags |= DT_SHORT_SHORT_INT;
        else if (*it == "long int")
            datatype->m_flags |= DT_LONG_INT;
        else if (*it == "long long int")
            datatype->m_flags |= DT_LONG_LONG_INT;
        else if (*it == "float")
            datatype->m_flags |= DT_FLOAT;
        else if (*it == "double")
            datatype->m_flags |= DT_DOUBLE;
        else if (*it == "char")
            datatype->m_flags |= DT_CHAR;
        else if (*it == "string")
            datatype->m_flags |= DT_STRING;
        else if (*it == "long double")
            datatype->m_flags |= DT_LONG_DOUBLE;
        else if (*it == "enum_const")
            datatype->m_flags |= DT_ENUM_CONST;
        else if (*it == "struct_obj")
            datatype->m_flags |= DT_STRUCT_OBJ;
        else if (*it == "class_obj")
            datatype->m_flags |= DT_CLASS_OBJ;
        else if (*it == "union_obj")
            datatype->m_flags |= DT_UNION_OBJ;

        if (*it == "const")
            datatype->m_flags |= SPECF_CONST;
        else if (*it == "signed")
            datatype->m_flags |= SPECF_SIGNED;
        else if (*it == "unsigned")
            datatype->m_flags |= SPECF_UNSIGNED;

        if (*it == "(")
        {
            datatype->m_isFunction = true;

            string paramTokens = "";

            it++;

            for (; it != tokens.end(); it++)
            {
                if (*it == "," || *it == ")")
                {
                    if (paramTokens != "")
                        datatype->m_functionParameters->push_back(
                            Datatype::create_from_string(paramTokens, true));

                    paramTokens = "";

                    if (*it == ",")
                        continue;
                    else
                        break;
                }

                paramTokens += (*it);
            }

        }

        if (*it == "*" || *it == "[]")
        {
            Datatype* pointer = new Datatype();
            pointer->m_isPointer = true;
            pointer->m_isArray = (*it == "[]") ? true : false;
            pointer->m_pointer = NULL;

            datatype->m_pointer = pointer;

            datatype = pointer;
        }

        if (*it == "&")
            datatype->m_isReference = true;
    }

    if ((datatype->m_flags & DT_INT) == DT_INT
    && (datatype->m_flags & SPECF_SIGNED) != SPECF_SIGNED
    && (datatype->m_flags & SPECF_UNSIGNED) != SPECF_UNSIGNED)
        datatype->m_flags |= SPECF_SIGNED;
}

///
/// Create a new pointer to a datatype, from a string.
///
Datatype* Datatype::create_from_string(const char* from_string, bool isParameter)
{
    return create_from_string((string)from_string, isParameter);
}

Datatype* Datatype::create_from_string(string from_string, bool isParameter)
{
    string* datatypeString = new (GC) string(from_string);
    Datatype* datatype = new Datatype(datatypeString, isParameter);
    return datatype;
}

///
/// Clone a datatype, returning the copy.
///
Datatype* Datatype::clone()
{
    if (this == NULL)
        return NULL;

    Datatype* datatype =
        new Datatype(
            m_flags,
            m_isFunction,
            new (GC) list<Datatype*>,
            m_isPointer,
            m_isArray,
            m_lexeme,
            m_isReference);

    if (m_pointer != NULL)
        datatype->m_pointer = m_pointer->clone();

    if (m_functionParameters != NULL)
    {
        if (m_functionParameters->empty())
            return datatype;

        std::copy(
            m_functionParameters->begin(),
            m_functionParameters->end(),
            std::front_inserter(*datatype->m_functionParameters));
    }

    return datatype;
}

///
/// Removes the function part of (defunctionalizes) this datatype.
///
void Datatype::defunctionalize()
{
    Datatype* pointer = const_cast<Datatype*>(this);

    while (pointer->m_pointer != NULL)
        pointer = pointer->m_pointer;

    pointer->m_isFunction = false;
}

///
/// Returns true if this is a function datatype.
///
bool Datatype::is_function() const
{
    Datatype* pointer = const_cast<Datatype*>(this);

    while (pointer->m_pointer != NULL)
        pointer = pointer->m_pointer;

    return pointer->m_isFunction;
}

///
/// Returns true if this is a pointer.
///
bool Datatype::is_pointer() const
{
    return m_pointer != NULL;
}

///
/// Returns true if this is a reference datatype.
///
bool Datatype::is_reference() const
{
    Datatype* pointer = const_cast<Datatype*>(this);

    while (pointer->m_pointer != NULL)
        pointer = pointer->m_pointer;

    return pointer->m_isReference;
}

///
/// Returns true if this is an array datatype.
///
bool Datatype::is_array() const
{
    Datatype* pointer = const_cast<Datatype*>(this);

    while (pointer->m_pointer != NULL)
        pointer = pointer->m_pointer;

    return pointer->m_isArray;
}

///
/// Returns the flags used to describe the datatype.
///
unsigned long int Datatype::get_flags() const
{
    return m_flags;
}

///
/// Returns the linked list of the function parameters that this datatype is declared with, if it is
/// indeed a function.
///
list<Datatype*>* Datatype::get_function_param_list() const
{
    return m_functionParameters;
}

///
/// Converts the datatype to a human readable string.
///
string Datatype::to_string(bool defunc, bool includeFuncParams)
{
    string result;

    if (m_flags & DT_VOID)
        result = "void";
    else if (m_flags & DT_BOOL)
        result = "bool";
    else if (m_flags & DT_INT)
        result = "int";
    else if (m_flags & DT_SHORT_INT)
        result = "short int";
    else if (m_flags & DT_SHORT_SHORT_INT)
        result = "short short int";
    else if (m_flags & DT_LONG_INT)
        result = "long int";
    else if (m_flags & DT_LONG_LONG_INT)
        result = "long long int";
    else if (m_flags & DT_FLOAT)
        result = "float";
    else if (m_flags & DT_DOUBLE)
        result = "double";
    else if (m_flags & DT_LONG_DOUBLE)
        result = "long double";
    else if (m_flags & DT_CHAR)
        result = "char";
    else if (m_flags & DT_STRING)
        result = "string";
    else if ((m_flags & DT_ENUM_CONST) == DT_ENUM_CONST)
        result = "enum_const";
    else if ((m_flags & DT_STRUCT_OBJ) == DT_STRUCT_OBJ)
        result = (string)"(struct)" + m_lexeme;
    else if ((m_flags & DT_CLASS_OBJ) == DT_CLASS_OBJ)
        result = (string)"(class)" + m_lexeme;
    else if ((m_flags & DT_UNION_OBJ) == DT_UNION_OBJ)
        result = (string)"(union)" + m_lexeme;
    else
        result = (string)UNKNOWN_TYPE;

    if (m_flags & SPECF_CONST)
        result = (string)"const " + result;
    if (m_flags & SPECF_SIGNED)
        result = (string)"signed " + result;
    else if (m_flags & SPECF_UNSIGNED)
        result = (string)"unsigned " + result;

    if (m_isPointer)
        result += (string)"*";

    if (m_isReference)
        result += "&";

    if (m_isFunction && !defunc)
    {
        result += '(';

        if (includeFuncParams && m_pointer == NULL)
        {
            Datatype* datatype = NULL;
            foreach (datatype, *m_functionParameters)
            {
                if (datatype != m_functionParameters->front())
                    result += ", ";
                result += datatype->to_string(false, false);
            }
        }

        result += ')';
    }

    if (m_pointer != NULL)
        result += m_pointer->to_string(false, includeFuncParams);

    return result;
}
